home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / js / calStorageCalendar.js < prev    next >
Text File  |  2008-03-18  |  92KB  |  2,334 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Oracle Corporation code.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  *  Oracle Corporation
  18.  * Portions created by the Initial Developer are Copyright (C) 2005, 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  23.  *   Joey Minta <jminta@gmail.com>
  24.  *   Dan Mosedale <dan.mosedale@oracle.com>
  25.  *   Thomas Benisch <thomas.benisch@sun.com>
  26.  *   Matthew Willis <lilmatt@mozilla.com>
  27.  *   Philipp Kewisch <mozilla@kewis.ch>
  28.  *   Daniel Boelzle <daniel.boelzle@sun.com>
  29.  *   Sebastian Schwieger <sebo.moz@googlemail.com>
  30.  *
  31.  * Alternatively, the contents of this file may be used under the terms of
  32.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  33.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  34.  * in which case the provisions of the GPL or the LGPL are applicable instead
  35.  * of those above. If you wish to allow use of your version of this file only
  36.  * under the terms of either the GPL or the LGPL, and not to allow others to
  37.  * use your version of this file under the terms of the MPL, indicate your
  38.  * decision by deleting the provisions above and replace them with the notice
  39.  * and other provisions required by the GPL or the LGPL. If you do not delete
  40.  * the provisions above, a recipient may use your version of this file under
  41.  * the terms of any one of the MPL, the GPL or the LGPL.
  42.  *
  43.  * ***** END LICENSE BLOCK ***** */
  44.  
  45. const kStorageServiceContractID = "@mozilla.org/storage/service;1";
  46. const kStorageServiceIID = Components.interfaces.mozIStorageService;
  47.  
  48. const kCalICalendar = Components.interfaces.calICalendar;
  49.  
  50. const kCalAttendeeContractID = "@mozilla.org/calendar/attendee;1";
  51. const kCalIAttendee = Components.interfaces.calIAttendee;
  52. var CalAttendee;
  53.  
  54. const kCalRecurrenceInfoContractID = "@mozilla.org/calendar/recurrence-info;1";
  55. const kCalIRecurrenceInfo = Components.interfaces.calIRecurrenceInfo;
  56. var CalRecurrenceInfo;
  57.  
  58. const kCalRecurrenceRuleContractID = "@mozilla.org/calendar/recurrence-rule;1";
  59. const kCalIRecurrenceRule = Components.interfaces.calIRecurrenceRule;
  60. var CalRecurrenceRule;
  61.  
  62. const kCalRecurrenceDateSetContractID = "@mozilla.org/calendar/recurrence-date-set;1";
  63. const kCalIRecurrenceDateSet = Components.interfaces.calIRecurrenceDateSet;
  64. var CalRecurrenceDateSet;
  65.  
  66. const kCalRecurrenceDateContractID = "@mozilla.org/calendar/recurrence-date;1";
  67. const kCalIRecurrenceDate = Components.interfaces.calIRecurrenceDate;
  68. var CalRecurrenceDate;
  69.  
  70. const kMozStorageStatementWrapperContractID = "@mozilla.org/storage/statement-wrapper;1";
  71. const kMozStorageStatementWrapperIID = Components.interfaces.mozIStorageStatementWrapper;
  72. var MozStorageStatementWrapper;
  73.  
  74. if (!kMozStorageStatementWrapperIID) {
  75.     dump("*** mozStorage not available, calendar/storage provider will not function\n");
  76. }
  77.  
  78. function initCalStorageCalendarComponent() {
  79.     CalAttendee = new Components.Constructor(kCalAttendeeContractID, kCalIAttendee);
  80.     CalRecurrenceInfo = new Components.Constructor(kCalRecurrenceInfoContractID, kCalIRecurrenceInfo);
  81.     CalRecurrenceRule = new Components.Constructor(kCalRecurrenceRuleContractID, kCalIRecurrenceRule);
  82.     CalRecurrenceDateSet = new Components.Constructor(kCalRecurrenceDateSetContractID, kCalIRecurrenceDateSet);
  83.     CalRecurrenceDate = new Components.Constructor(kCalRecurrenceDateContractID, kCalIRecurrenceDate);
  84.     MozStorageStatementWrapper = new Components.Constructor(kMozStorageStatementWrapperContractID, kMozStorageStatementWrapperIID);
  85. }
  86.  
  87. //
  88. // calStorageCalendar.js
  89. //
  90.  
  91. const CAL_ITEM_TYPE_EVENT = 0;
  92. const CAL_ITEM_TYPE_TODO = 1;
  93.  
  94. // bitmasks
  95. const CAL_ITEM_FLAG_PRIVATE = 1;
  96. const CAL_ITEM_FLAG_HAS_ATTENDEES = 2;
  97. const CAL_ITEM_FLAG_HAS_PROPERTIES = 4;
  98. const CAL_ITEM_FLAG_EVENT_ALLDAY = 8;
  99. const CAL_ITEM_FLAG_HAS_RECURRENCE = 16;
  100. const CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32;
  101.  
  102. const USECS_PER_SECOND = 1000000;
  103.  
  104. //
  105. // Storage helpers
  106. //
  107.  
  108. function createStatement (dbconn, sql) {
  109.     try {
  110.         var stmt = dbconn.createStatement(sql);
  111.         var wrapper = MozStorageStatementWrapper();
  112.         wrapper.initialize(stmt);
  113.         return wrapper;
  114.     } catch (e) {
  115.         Components.utils.reportError(
  116.             "mozStorage exception: createStatement failed, statement: '" + 
  117.             sql + "', error: '" + dbconn.lastErrorString + "' - " + e);
  118.     }
  119.  
  120.     return null;
  121. }
  122.  
  123. function getInUtcOrKeepFloating(dt) {
  124.     var tz = dt.timezone;
  125.     if (tz.isFloating || tz.isUTC) {
  126.         return dt;
  127.     } else {
  128.         return dt.getInTimezone(UTC());
  129.     }
  130. }
  131.  
  132. function textToDate(d) {
  133.     var dval;
  134.     var tz = "UTC";
  135.  
  136.     if (d[0] == 'Z') {
  137.         var strs = d.substr(2).split(":");
  138.         dval = parseInt(strs[0]);
  139.         tz = strs[1].replace(/%:/g, ":").replace(/%%/g, "%");
  140.     } else {
  141.         dval = parseInt(d.substr(2));
  142.     }
  143.  
  144.     var date;
  145.     if (d[0] == 'U' || d[0] == 'Z') {
  146.         date = newDateTime(dval, tz);
  147.     } else if (d[0] == 'L') {
  148.         // is local time
  149.         date = newDateTime(dval, "floating");
  150.     }
  151.  
  152.     if (d[1] == 'D')
  153.         date.isDate = true;
  154.     return date;
  155. }
  156.  
  157. function dateToText(d) {
  158.     var datestr;
  159.     var tz = null;
  160.     if (!d.timezone.isFloating) {
  161.         if (d.timezone.isUTC) {
  162.             datestr = "U";
  163.         } else {
  164.             datestr = "Z";
  165.             tz = d.timezone.tzid;
  166.         }
  167.     } else {
  168.         datestr = "L";
  169.     }
  170.  
  171.     if (d.isDate) {
  172.         datestr += "D";
  173.     } else {
  174.         datestr += "T";
  175.     }
  176.  
  177.     datestr += d.nativeTime;
  178.  
  179.     if (tz) {
  180.         // replace '%' with '%%', then replace ':' with '%:'
  181.         tz = tz.replace(/%/g, "%%");
  182.         tz = tz.replace(/:/g, "%:");
  183.         datestr += ":" + tz;
  184.     }
  185.     return datestr;
  186. }
  187.  
  188. // 
  189. // other helpers
  190. //
  191.  
  192. function calStorageTimezone(comp) {
  193.     this.wrappedJSObject = this;
  194.     this.provider = null;
  195.     this.component = comp;
  196.     this.tzid = comp.getFirstProperty("TZID").value;
  197.     this.isUTC = false;
  198.     this.isFloating = false;
  199.     this.latitude = "";
  200.     this.longitude = "";
  201. }
  202. calStorageTimezone.prototype = {
  203.     toString: function() {
  204.         return this.component.toString();
  205.     }
  206. };
  207. var gForeignTimezonesCache = {};
  208.  
  209. function newDateTime(aNativeTime, aTimezone) {
  210.     var t = createDateTime();
  211.     t.nativeTime = aNativeTime;
  212.     if (aTimezone) {
  213.         var tz;
  214.         if (aTimezone.indexOf("BEGIN:VTIMEZONE") == 0) {
  215.             tz = gForeignTimezonesCache[aTimezone]; // using full definition as key
  216.             if (!tz) {
  217.                 try {
  218.                     // cannot cope without parent VCALENDAR:
  219.                     var comp = getIcsService().parseICS("BEGIN:VCALENDAR\n" + aTimezone + "\nEND:VCALENDAR", null);
  220.                     tz = new calStorageTimezone(comp.getFirstSubcomponent("VTIMEZONE"));
  221.                     gForeignTimezonesCache[aTimezone] = tz;
  222.                 } catch (exc) {
  223.                     ASSERT(false, exc);
  224.                 }
  225.             }
  226.         } else {
  227.             tz = getTimezoneService().getTimezone(aTimezone);
  228.         }
  229.         if (tz) {
  230.             t = t.getInTimezone(tz);
  231.         } else {
  232.             ASSERT(false, "timezone not available: " + aTimezone);
  233.         }
  234.     } else {
  235.         t.timezone = floating();
  236.     }
  237.  
  238.     return t;
  239. }
  240.  
  241. //
  242. // calStorageCalendar
  243. //
  244.  
  245. function calStorageCalendar() {
  246.     this.initProviderBase();
  247.     this.mItemCache = {};
  248.     this.mRecEventCache = {};
  249.     this.mRecTodoCache = {};
  250. }
  251.  
  252. calStorageCalendar.prototype = {
  253.     __proto__: calProviderBase.prototype,
  254.     //
  255.     // private members
  256.     //
  257.     mDB: null,
  258.     mDBTwo: null,
  259.     mCalId: 0,
  260.     mItemCache: null,
  261.     mRecItemCacheInited: false,
  262.     mRecEventCache: null,
  263.     mRecTodoCache: null,
  264.  
  265.     //
  266.     // nsISupports interface
  267.     // 
  268.     QueryInterface: function (aIID) {
  269.         return doQueryInterface(this, calStorageCalendar.prototype, aIID,
  270.                                 [Components.interfaces.calICalendarProvider,
  271.                                  Components.interfaces.calISyncCalendar]);
  272.     },
  273.  
  274.     //
  275.     // calICalendarProvider interface
  276.     //
  277.     get prefChromeOverlay() {
  278.         return null;
  279.     },
  280.  
  281.     get displayName() {
  282.         return calGetString("calendar", "storageName");
  283.     },
  284.  
  285.     createCalendar: function stor_createCal() {
  286.         throw NS_ERROR_NOT_IMPLEMENTED;
  287.     },
  288.  
  289.     deleteCalendar: function stor_deleteCal(cal, listener) {
  290.         cal = cal.wrappedJSObject;
  291.  
  292.         for (var i in this.mDeleteEventExtras) {
  293.             this.mDeleteEventExtras[i].params.cal_id = cal.mCalId;
  294.             this.mDeleteEventExtras[i].execute();
  295.             this.mDeleteEventExtras[i].reset();
  296.         }
  297.  
  298.         for (var i in this.mDeleteTodoExtras) {
  299.             this.mDeleteTodoExtras[i].params.cal_id = cal.mCalId;
  300.             this.mDeleteTodoExtras[i].execute();
  301.             this.mDeleteTodoExtras[i].reset();
  302.         }
  303.  
  304.         this.mDeleteAllEvents.params.cal_id = cal.mCalId;
  305.         this.mDeleteAllEvents.execute();
  306.         this.mDeleteAllEvents.reset();
  307.  
  308.         this.mDeleteAllTodos.params.cal_id = cal.mCalId;
  309.         this.mDeleteAllTodos.execute();
  310.         this.mDeleteAllTodos.reset();
  311.  
  312.         try {
  313.             listener.onDeleteCalendar(cal, Components.results.NS_OK, null);
  314.         } catch (ex) {
  315.         }
  316.     },
  317.  
  318.     mRelaxedMode: undefined,
  319.     get relaxedMode() {
  320.         if (this.mRelaxedMode === undefined) {
  321.             this.mRelaxedMode = this.getProperty("relaxedMode");
  322.         }
  323.         return this.mRelaxedMode;
  324.     },
  325.  
  326.     //
  327.     // calICalendar interface
  328.     //
  329.  
  330.     getProperty: function stor_getProperty(aName) {
  331.         switch (aName) {
  332.             case "cache.supported":
  333.                 return false;
  334.             case "requiresNetwork":
  335.                 return false;
  336.         }
  337.         return this.__proto__.__proto__.getProperty.apply(this, arguments);
  338.     },
  339.  
  340.     // readonly attribute AUTF8String type;
  341.     get type() { return "storage"; },
  342.  
  343.     // attribute nsIURI uri;
  344.     get uri() {
  345.         return this.mUri;
  346.     },
  347.     set uri(aUri) {
  348.         // we can only load once
  349.         if (this.mUri) {
  350.             throw Components.results.NS_ERROR_FAILURE;
  351.         }
  352.  
  353.         var id = 0;
  354.  
  355.         // check if there's a ?id=
  356.         var path = aUri.path;
  357.         var pos = path.indexOf("?id=");
  358.  
  359.         if (pos != -1) {
  360.             id = parseInt(path.substr(pos+4));
  361.             path = path.substr(0, pos);
  362.         }
  363.  
  364.         var dbService;
  365.         if (aUri.scheme == "file") {
  366.             var fileURL = aUri.QueryInterface(Components.interfaces.nsIFileURL);
  367.             if (!fileURL)
  368.                 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  369.  
  370.             // open the database
  371.             dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
  372.             this.mDB = dbService.openDatabase (fileURL.file);
  373.             this.mDBTwo = dbService.openDatabase (fileURL.file);
  374.         } else if (aUri.scheme == "moz-profile-calendar") {
  375.             dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
  376.             if ("getProfileStorage" in dbService) {
  377.               // 1.8 branch
  378.               this.mDB = dbService.getProfileStorage("profile");
  379.               this.mDBTwo = dbService.getProfileStorage("profile");
  380.             } else {
  381.               // trunk
  382.               this.mDB = dbService.openSpecialDatabase("profile");
  383.               this.mDBTwo = dbService.openSpecialDatabase("profile");
  384.             }
  385.         }
  386.  
  387.         this.initDB();
  388.  
  389.         this.mCalId = id;
  390.         this.mUri = aUri;
  391.     },
  392.  
  393.     refresh: function() {
  394.         // no-op
  395.     },
  396.  
  397.     // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
  398.     addItem: function (aItem, aListener) {
  399.         var newItem = aItem.clone();
  400.         return this.adoptItem(newItem, aListener);
  401.     },
  402.  
  403.     // void adoptItem( in calIItemBase aItem, in calIOperationListener aListener );
  404.     adoptItem: function (aItem, aListener) {
  405.         if (this.readOnly) {
  406.             if (aListener) {
  407.                 aListener.onOperationComplete(this.superCalenddar,
  408.                                               Components.interfaces.calIErrors.CAL_IS_READONLY,
  409.                                               aListener.ADD,
  410.                                               null,
  411.                                               "Calendar is readonly");
  412.             }
  413.             return;
  414.         }
  415.         // Ensure that we're looking at the base item
  416.         // if we were given an occurrence.  Later we can
  417.         // optimize this.
  418.         if (aItem.parentItem != aItem) {
  419.             aItem.parentItem.recurrenceInfo.modifyException(aItem);
  420.         }
  421.         aItem = aItem.parentItem;
  422.  
  423.         if (aItem.id == null) {
  424.             // is this an error?  Or should we generate an IID?
  425.             aItem.id = getUUID();
  426.         } else {
  427.             var olditem = this.getItemById(aItem.id);
  428.             if (olditem) {
  429.                 if (this.relaxedMode) {
  430.                     // we possibly want to interact with the user before deleting
  431.                     this.deleteItemById(aItem.id);
  432.                 }
  433.                 else {
  434.                     if (aListener)
  435.                         aListener.onOperationComplete(this.superCalendar,
  436.                                                       Components.interfaces.calIErrors.DUPLICATE_ID,
  437.                                                       aListener.ADD,
  438.                                                       aItem.id,
  439.                                                       "ID already exists for addItem");
  440.                     return;
  441.                 }
  442.             }
  443.         }
  444.  
  445.         aItem.calendar = this.superCalendar;
  446.         aItem.makeImmutable();
  447.  
  448.         this.flushItem (aItem, null);
  449.  
  450.         // notify the listener
  451.         if (aListener)
  452.             aListener.onOperationComplete (this.superCalendar,
  453.                                            Components.results.NS_OK,
  454.                                            aListener.ADD,
  455.                                            aItem.id,
  456.                                            aItem);
  457.  
  458.         // notify observers
  459.         this.mObservers.notify("onAddItem", [aItem]);
  460.     },
  461.  
  462.     // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
  463.     modifyItem: function (aNewItem, aOldItem, aListener) {
  464.         if (this.readOnly) {
  465.             if (aListener) {
  466.                 aListener.onOperationComplete(this.superCalenddar,
  467.                                               Components.interfaces.calIErrors.CAL_IS_READONLY,
  468.                                               aListener.MODIFY,
  469.                                               null,
  470.                                               "Calendar is readonly");
  471.             }
  472.             return null;
  473.         }
  474.         if (!aNewItem) {
  475.             throw Components.results.NS_ERROR_INVALID_ARG;
  476.         }
  477.  
  478.         var this_ = this;
  479.         function reportError(errStr, errId) {
  480.             if (aListener) {
  481.                 aListener.onOperationComplete(this_.superCalendar,
  482.                                               errId ? errId : Components.results.NS_ERROR_FAILURE,
  483.                                               aListener.MODIFY,
  484.                                               aNewItem.id,
  485.                                               errStr);
  486.             }
  487.             return null;
  488.         }
  489.  
  490.         if (aNewItem.id == null) {
  491.             // this is definitely an error
  492.             return reportError("ID for modifyItem item is null");
  493.         }
  494.  
  495.         // Ensure that we're looking at the base item if we were given an
  496.         // occurrence.  Later we can optimize this.
  497.         var modifiedItem = aNewItem.clone();
  498.         if (modifiedItem.parentItem != modifiedItem) {
  499.             modifiedItem.parentItem.recurrenceInfo.modifyException(modifiedItem);
  500.             modifiedItem = modifiedItem.parentItem;
  501.         }
  502.  
  503.         if (this.relaxedMode) {
  504.             if (!aOldItem) {
  505.                 aOldItem = this.getItemById(aNewItem.id) || aNewItem;
  506.             }
  507.             aOldItem = aOldItem.parentItem;
  508.         } else {
  509.             var storedOldItem = (aOldItem ? this.getItemById(aOldItem.id) : null);
  510.             if (!aOldItem || !storedOldItem) {
  511.                 // no old item found?  should be using addItem, then.
  512.                 return reportError("ID does not already exist for modifyItem");
  513.             }
  514.             aOldItem = aOldItem.parentItem;
  515.  
  516.             if (aOldItem.generation != storedOldItem.generation) {
  517.                 return reportError("generation too old for for modifyItem");
  518.             }
  519.  
  520.             if (aOldItem.generation == modifiedItem.generation) { // has been cloned and modified
  521.                 // Only take care of incrementing the generation if relaxed mode is
  522.                 // off. Users of relaxed mode need to take care of this themselves.
  523.                 modifiedItem.generation += 1;
  524.             }
  525.         }
  526.  
  527.         modifiedItem.makeImmutable();
  528.         this.flushItem (modifiedItem, aOldItem);
  529.  
  530.         if (aListener) {
  531.             aListener.onOperationComplete (this.superCalendar,
  532.                                            Components.results.NS_OK,
  533.                                            aListener.MODIFY,
  534.                                            modifiedItem.id,
  535.                                            modifiedItem);
  536.         }
  537.  
  538.         // notify observers
  539.         this.mObservers.notify("onModifyItem", [modifiedItem, aOldItem]);
  540.         return null;
  541.     },
  542.  
  543.     // void deleteItem( in string id, in calIOperationListener aListener );
  544.     deleteItem: function (aItem, aListener) {
  545.         if (this.readOnly) {
  546.             if (aListener) {
  547.                 aListener.onOperationComplete(this.superCalenddar,
  548.                                               Components.interfaces.calIErrors.CAL_IS_READONLY,
  549.                                               aListener.DELETE,
  550.                                               null,
  551.                                               "Calendar is readonly");
  552.             }
  553.             return;
  554.         }
  555.         if (aItem.parentItem != aItem) {
  556.             aItem.parentItem.recurrenceInfo.removeExceptionFor(aItem.recurrenceId);
  557.             // xxx todo: would we want to support this case? Removing an occurrence currently results
  558.             //           in a modifyItem(parent)
  559.             return;
  560.         }
  561.  
  562.         if (aItem.id == null) {
  563.             if (aListener)
  564.                 aListener.onOperationComplete (this.superCalendar,
  565.                                                Components.results.NS_ERROR_FAILURE,
  566.                                                aListener.DELETE,
  567.                                                null,
  568.                                                "ID is null for deleteItem");
  569.             return;
  570.         }
  571.  
  572.         this.deleteItemById(aItem.id);
  573.  
  574.         if (aListener)
  575.             aListener.onOperationComplete (this.superCalendar,
  576.                                            Components.results.NS_OK,
  577.                                            aListener.DELETE,
  578.                                            aItem.id,
  579.                                            aItem);
  580.  
  581.         // notify observers 
  582.         this.mObservers.notify("onDeleteItem", [aItem]);
  583.     },
  584.  
  585.     // void getItem( in string id, in calIOperationListener aListener );
  586.     getItem: function (aId, aListener) {
  587.         if (!aListener)
  588.             return;
  589.  
  590.         var item = this.getItemById (aId);
  591.         if (!item) {
  592.             aListener.onOperationComplete (this.superCalendar,
  593.                                            Components.results.NS_ERROR_FAILURE,
  594.                                            aListener.GET,
  595.                                            aId,
  596.                                            "ID doesn't exist for getItem");
  597.         }
  598.  
  599.         var item_iid = null;
  600.         if (item instanceof Components.interfaces.calIEvent)
  601.             item_iid = Components.interfaces.calIEvent;
  602.         else if (item instanceof Components.interfaces.calITodo)
  603.             item_iid = Components.interfaces.calITodo;
  604.         else {
  605.             aListener.onOperationComplete (this.superCalendar,
  606.                                            Components.results.NS_ERROR_FAILURE,
  607.                                            aListener.GET,
  608.                                            aId,
  609.                                            "Can't deduce item type based on QI");
  610.             return;
  611.         }
  612.  
  613.         aListener.onGetResult (this.superCalendar,
  614.                                Components.results.NS_OK,
  615.                                item_iid, null,
  616.                                1, [item]);
  617.  
  618.         aListener.onOperationComplete (this.superCalendar,
  619.                                        Components.results.NS_OK,
  620.                                        aListener.GET,
  621.                                        aId,
  622.                                        null);
  623.     },
  624.  
  625.     // void getItems( in unsigned long aItemFilter, in unsigned long aCount, 
  626.     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
  627.     //                in calIOperationListener aListener );
  628.     getItems: function (aItemFilter, aCount,
  629.                         aRangeStart, aRangeEnd, aListener)
  630.     {
  631.         //var profStartTime = Date.now();
  632.         if (!aListener)
  633.             return;
  634.  
  635.         var self = this;
  636.  
  637.         var itemsFound = Array();
  638.         var startTime = -0x7fffffffffffffff;
  639.         // endTime needs to be the max value a PRTime can be
  640.         var endTime = 0x7fffffffffffffff;
  641.         var count = 0;
  642.         if (aRangeStart)
  643.             startTime = aRangeStart.nativeTime;
  644.         if (aRangeEnd)
  645.             endTime = aRangeEnd.nativeTime;
  646.  
  647.         var wantEvents = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_EVENT) != 0);
  648.         var wantTodos = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_TODO) != 0);
  649.         var asOccurrences = ((aItemFilter & kCalICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0);
  650.         if (!wantEvents && !wantTodos) {
  651.             // nothing to do
  652.             aListener.onOperationComplete (this.superCalendar,
  653.                                            Components.results.NS_OK,
  654.                                            aListener.GET,
  655.                                            null,
  656.                                            null);
  657.             return;
  658.         }
  659.  
  660.         this.assureRecurringItemCaches();
  661.  
  662.         var itemCompletedFilter = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_YES) != 0);
  663.         var itemNotCompletedFilter = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_NO) != 0);
  664.  
  665.         function checkCompleted(item) {
  666.             return (item.isCompleted ? itemCompletedFilter : itemNotCompletedFilter);
  667.         }
  668.  
  669.         // sending items to the listener 1 at a time sucks. instead,
  670.         // queue them up.
  671.         // if we ever have more than maxQueueSize items outstanding,
  672.         // call the listener.  Calling with null theItems forces
  673.         // a send and a queue clear.
  674.         var maxQueueSize = 10;
  675.         var queuedItems = [ ];
  676.         var queuedItemsIID;
  677.         function queueItems(theItems, theIID) {
  678.             // if we're about to start sending a different IID,
  679.             // flush the queue
  680.             if (theIID && queuedItemsIID != theIID) {
  681.                 if (queuedItemsIID)
  682.                     queueItems(null);
  683.                 queuedItemsIID = theIID;
  684.             }
  685.  
  686.             if (theItems)
  687.                 queuedItems = queuedItems.concat(theItems);
  688.  
  689.             if (queuedItems.length != 0 && (!theItems || queuedItems.length > maxQueueSize)) {
  690.                 //var listenerStart = Date.now();
  691.                 aListener.onGetResult(self.superCalendar,
  692.                                       Components.results.NS_OK,
  693.                                       queuedItemsIID, null,
  694.                                       queuedItems.length, queuedItems);
  695.                 //var listenerEnd = Date.now();
  696.                 //dump ("++++ listener callback took: " + (listenerEnd - listenerStart) + " ms\n");
  697.  
  698.                 queuedItems = [ ];
  699.             }
  700.         }
  701.  
  702.         // helper function to handle converting a row to an item,
  703.         // expanding occurrences, and queue the items for the listener
  704.         function handleResultItem(item, theIID, optionalFilterFunc) {
  705.             var expandedItems = [];
  706.             if (item.recurrenceInfo) {
  707.                 if (asOccurrences) {
  708.                     // If the item is recurring, get all ocurrences that fall in
  709.                     // the range. If the item doesn't fall into the range at all,
  710.                     // this expands to 0 items.
  711.                     expandedItems = item.recurrenceInfo
  712.                                     .getOccurrences(aRangeStart, aRangeEnd, 0, {});
  713.                 } else if (checkIfInRange(item, aRangeStart, aRangeEnd)) {
  714.                     // If no occurrences are wanted, check only the parent item.
  715.                     // This will be changed with bug 416975.
  716.                     expandedItems = [ item ];
  717.                 }
  718.             } else {
  719.                 // non-recurring item
  720.                 expandedItems = [ item ];
  721.             }
  722.  
  723.             if (expandedItems.length && optionalFilterFunc) {
  724.                 expandedItems = expandedItems.filter(optionalFilterFunc);
  725.             }
  726.  
  727.             queueItems (expandedItems, theIID);
  728.             return expandedItems.length;
  729.         }
  730.  
  731.         // check the count and send end if count is exceeded
  732.         function checkCount() {
  733.             if (aCount && count >= aCount) {
  734.                 // flush queue
  735.                 queueItems(null);
  736.  
  737.                 // send operation complete
  738.                 aListener.onOperationComplete (self.superCalendar,
  739.                                                Components.results.NS_OK,
  740.                                                aListener.GET,
  741.                                                null,
  742.                                                null);
  743.  
  744.                 // tell caller we're done
  745.                 return true;
  746.             }
  747.  
  748.             return false;
  749.         }
  750.  
  751.         // First fetch all the events
  752.         if (wantEvents) {
  753.             var sp;             // stmt params
  754.             var resultItems = [];
  755.  
  756.             // first get non-recurring events that happen to fall within the range
  757.             //
  758.             sp = this.mSelectNonRecurringEventsByRange.params;
  759.             sp.cal_id = this.mCalId;
  760.             sp.range_start = startTime;
  761.             sp.range_end = endTime;
  762.             sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
  763.             sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
  764.  
  765.             while (this.mSelectNonRecurringEventsByRange.step()) {
  766.                 var row = this.mSelectNonRecurringEventsByRange.row;
  767.                 var item = this.getEventFromRow(row, {});
  768.                 resultItems.push(item);
  769.             }
  770.             this.mSelectNonRecurringEventsByRange.reset();
  771.  
  772.             // process the non-recurring events:
  773.             for each (var evitem in resultItems) {
  774.                 count += handleResultItem(evitem, Components.interfaces.calIEvent);
  775.                 if (checkCount()) {
  776.                     return;
  777.                 }
  778.             }
  779.  
  780.             // process the recurring events from the cache
  781.             for each (var evitem in this.mRecEventCache) {
  782.                 count += handleResultItem(evitem, Components.interfaces.calIEvent);
  783.                 if (checkCount()) {
  784.                     return;
  785.                 }
  786.             }
  787.         }
  788.  
  789.         // if todos are wanted, do them next
  790.         if (wantTodos) {
  791.             var sp;             // stmt params
  792.             var resultItems = [];
  793.  
  794.             // first get non-recurring todos that happen to fall within the range
  795.             sp = this.mSelectNonRecurringTodosByRange.params;
  796.             sp.cal_id = this.mCalId;
  797.             sp.range_start = startTime;
  798.             sp.range_end = endTime;
  799.             sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
  800.             sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
  801.  
  802.             while (this.mSelectNonRecurringTodosByRange.step()) {
  803.                 var row = this.mSelectNonRecurringTodosByRange.row;
  804.                 resultItems.push(this.getTodoFromRow(row, {}));
  805.             }
  806.             this.mSelectNonRecurringTodosByRange.reset();
  807.  
  808.             // process the non-recurring todos:
  809.             for each (var todoitem in resultItems) {
  810.                 count += handleResultItem(todoitem, Components.interfaces.calITodo, checkCompleted);
  811.                 if (checkCount()) {
  812.                     return;
  813.                 }
  814.             }
  815.  
  816.             // Note: Reading the code, completed *occurrences* seems to be broken, because
  817.             //       only the parent item has been filtered; I fixed that.
  818.             //       Moreover item.todo_complete etc seems to be a leftover...
  819.  
  820.             // process the recurring todos from the cache
  821.             for each (var todoitem in this.mRecTodoCache) {
  822.                 count += handleResultItem(todoitem, Components.interfaces.calITodo, checkCompleted);
  823.                 if (checkCount()) {
  824.                     return;
  825.                 }
  826.             }
  827.         }
  828.  
  829.         // flush the queue
  830.         queueItems(null);
  831.  
  832.         // and finish
  833.         aListener.onOperationComplete (this.superCalendar,
  834.                                        Components.results.NS_OK,
  835.                                        aListener.GET,
  836.                                        null,
  837.                                        null);
  838.  
  839.         //var profEndTime = Date.now();
  840.         //dump ("++++ getItems took: " + (profEndTime - profStartTime) + " ms\n");
  841.     },
  842.  
  843.     //
  844.     // Helper functions
  845.     //
  846.  
  847.     //
  848.     // database handling
  849.     //
  850.  
  851.     // initialize the database schema.
  852.     // needs to do some version checking
  853.     initDBSchema: function () {
  854.         for (table in sqlTables) {
  855.             try {
  856.                 this.mDB.executeSimpleSQL("DROP TABLE " + table);
  857.             } catch (e) { }
  858.             this.mDB.createTable(table, sqlTables[table]);
  859.         }
  860.  
  861.         // Add a version stamp to the schema
  862.         this.mDB.executeSimpleSQL("INSERT INTO cal_calendar_schema_version VALUES(" + this.DB_SCHEMA_VERSION + ")");
  863.     },
  864.  
  865.     DB_SCHEMA_VERSION: 8,
  866.  
  867.     /** 
  868.      * @return      db schema version
  869.      * @exception   various, depending on error
  870.      */
  871.     getVersion: function calStorageGetVersion() {
  872.         var selectSchemaVersion;
  873.         var version = null;
  874.  
  875.         try {
  876.             selectSchemaVersion = createStatement(this.mDB, 
  877.                                   "SELECT version FROM " +
  878.                                   "cal_calendar_schema_version LIMIT 1");
  879.             if (selectSchemaVersion.step()) {
  880.                 version = selectSchemaVersion.row.version;
  881.             }
  882.             selectSchemaVersion.reset();
  883.  
  884.             if (version !== null) {
  885.                 // This is the only place to leave this function gracefully.
  886.                 return version;
  887.             }
  888.         } catch (e) {
  889.             if (selectSchemaVersion) {
  890.                 selectSchemaVersion.reset();
  891.             }
  892.             dump ("++++++++++++ calStorageGetVersion() error: " +
  893.                   this.mDB.lastErrorString + "\n");
  894.             Components.utils.reportError("Error getting storage calendar " +
  895.                                          "schema version! DB Error: " + 
  896.                                          this.mDB.lastErrorString);
  897.             throw e;
  898.         }
  899.  
  900.         throw "cal_calendar_schema_version SELECT returned no results";
  901.     },
  902.  
  903.     upgradeDB: function (oldVersion) {
  904.         // some common helpers
  905.         function addColumn(db, tableName, colName, colType) {
  906.             db.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
  907.         }
  908.  
  909.         if (oldVersion == 2) {
  910.             dump ("**** Upgrading schema from 2 -> 3\n");
  911.  
  912.             this.mDB.beginTransaction();
  913.             try {
  914.                 // the change between 2 and 3 includes the splitting of cal_items into
  915.                 // cal_events and cal_todos, and the addition of columns for
  916.                 // event_start_tz, event_end_tz, todo_entry_tz, todo_due_tz.
  917.                 // These need to default to "UTC" if their corresponding time is
  918.                 // given, since that's what the default was for v2 calendars
  919.  
  920.                 // create the two new tables
  921.                 try { this.mDB.executeSimpleSQL("DROP TABLE cal_events; DROP TABLE cal_todos;"); } catch (e) { }
  922.                 this.mDB.createTable("cal_events", sqlTables["cal_events"]);
  923.                 this.mDB.createTable("cal_todos", sqlTables["cal_todos"]);
  924.  
  925.                 // copy stuff over
  926.                 var eventCols = ["cal_id", "id", "time_created", "last_modified", "title",
  927.                                  "priority", "privacy", "ical_status", "flags",
  928.                                  "event_start", "event_end", "event_stamp"];
  929.                 var todoCols = ["cal_id", "id", "time_created", "last_modified", "title",
  930.                                 "priority", "privacy", "ical_status", "flags",
  931.                                 "todo_entry", "todo_due", "todo_completed", "todo_complete"];
  932.  
  933.                 this.mDB.executeSimpleSQL("INSERT INTO cal_events(" + eventCols.join(",") + ") " +
  934.                                           "     SELECT " + eventCols.join(",") +
  935.                                           "       FROM cal_items WHERE item_type = 0");
  936.                 this.mDB.executeSimpleSQL("INSERT INTO cal_todos(" + todoCols.join(",") + ") " +
  937.                                           "     SELECT " + todoCols.join(",") +
  938.                                           "       FROM cal_items WHERE item_type = 1");
  939.  
  940.                 // now fix up the new _tz columns
  941.                 this.mDB.executeSimpleSQL("UPDATE cal_events SET event_start_tz = 'UTC' WHERE event_start IS NOT NULL");
  942.                 this.mDB.executeSimpleSQL("UPDATE cal_events SET event_end_tz = 'UTC' WHERE event_end IS NOT NULL");
  943.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_entry_tz = 'UTC' WHERE todo_entry IS NOT NULL");
  944.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_due_tz = 'UTC' WHERE todo_due IS NOT NULL");
  945.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_completed_tz = 'UTC' WHERE todo_completed IS NOT NULL");
  946.  
  947.                 // finally update the version
  948.                 this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (3);");
  949.  
  950.                 this.mDB.commitTransaction();
  951.  
  952.                 oldVersion = 3;
  953.             } catch (e) {
  954.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  955.                 Components.utils.reportError("Upgrade failed! DB Error: " + 
  956.                                              this.mDB.lastErrorString);
  957.                 this.mDB.rollbackTransaction();
  958.                 throw e;
  959.             }
  960.         }
  961.  
  962.         if (oldVersion == 3) {
  963.             dump ("**** Upgrading schema from 3 -> 4\n");
  964.  
  965.             this.mDB.beginTransaction();
  966.             try {
  967.                 // the change between 3 and 4 is the addition of
  968.                 // recurrence_id and recurrence_id_tz columns to
  969.                 // cal_events, cal_todos, cal_attendees, and cal_properties
  970.                 addColumn(this.mDB, "cal_events", "recurrence_id", "INTEGER");
  971.                 addColumn(this.mDB, "cal_events", "recurrence_id_tz", "VARCHAR");
  972.  
  973.                 addColumn(this.mDB, "cal_todos", "recurrence_id", "INTEGER");
  974.                 addColumn(this.mDB, "cal_todos", "recurrence_id_tz", "VARCHAR");
  975.  
  976.                 addColumn(this.mDB, "cal_attendees", "recurrence_id", "INTEGER");
  977.                 addColumn(this.mDB, "cal_attendees", "recurrence_id_tz", "VARCHAR");
  978.  
  979.                 addColumn(this.mDB, "cal_properties", "recurrence_id", "INTEGER");
  980.                 addColumn(this.mDB, "cal_properties", "recurrence_id_tz", "VARCHAR");
  981.  
  982.                 this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (4);");
  983.                 this.mDB.commitTransaction();
  984.  
  985.                 oldVersion = 4;
  986.             } catch (e) {
  987.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  988.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  989.                                              this.mDB.lastErrorString);
  990.                 this.mDB.rollbackTransaction();
  991.                 throw e;
  992.             }
  993.         }
  994.  
  995.         if (oldVersion == 4) {
  996.             dump ("**** Upgrading schema from 4 -> 5\n");
  997.  
  998.             this.mDB.beginTransaction();
  999.             try {
  1000.                 // the change between 4 and 5 is the addition of alarm_offset
  1001.                 // and alarm_last_ack columns.  The alarm_time column is not
  1002.                 // used in this version, but will likely return in future versions
  1003.                 // so it is not being removed
  1004.                 addColumn(this.mDB, "cal_events", "alarm_offset", "INTEGER");
  1005.                 addColumn(this.mDB, "cal_events", "alarm_related", "INTEGER");
  1006.                 addColumn(this.mDB, "cal_events", "alarm_last_ack", "INTEGER");
  1007.  
  1008.                 addColumn(this.mDB, "cal_todos", "alarm_offset", "INTEGER");
  1009.                 addColumn(this.mDB, "cal_todos", "alarm_related", "INTEGER");
  1010.                 addColumn(this.mDB, "cal_todos", "alarm_last_ack", "INTEGER");
  1011.  
  1012.                 this.mDB.executeSimpleSQL("UPDATE cal_calendar_schema_version SET version = 5;");
  1013.                 this.mDB.commitTransaction();
  1014.                 oldVersion = 5;
  1015.             } catch (e) {
  1016.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  1017.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  1018.                                              this.mDB.lastErrorString);
  1019.                 this.mDB.rollbackTransaction();
  1020.                 throw e;
  1021.             }
  1022.         }
  1023.  
  1024.         if (oldVersion == 5) {
  1025.             dump ("**** Upgrading schema from 5 -> 6\n");
  1026.  
  1027.             this.mDB.beginTransaction();
  1028.             try {
  1029.                 // Schema changes between v5 and v6:
  1030.                 //
  1031.                 // - Change all STRING columns to TEXT to avoid SQLite's
  1032.                 //   "feature" where it will automatically convert strings to
  1033.                 //   numbers (ex: 10e4 -> 10000). See bug 333688.
  1034.  
  1035.  
  1036.                 // Create the new tables.
  1037.                 var tableNames = ["cal_events", "cal_todos", "cal_attendees",
  1038.                                   "cal_recurrence", "cal_properties"];
  1039.  
  1040.                 var query = "";
  1041.                 try { 
  1042.                     for (var i in tableNames) {
  1043.                         query += "DROP TABLE " + tableNames[i] + "_v6;"
  1044.                     }
  1045.                     this.mDB.executeSimpleSQL(query);
  1046.                 } catch (e) {
  1047.                     // We should get exceptions for trying to drop tables
  1048.                     // that don't (shouldn't) exist.
  1049.                 }
  1050.  
  1051.                 this.mDB.createTable("cal_events_v6", sqlTables["cal_events"]);
  1052.                 this.mDB.createTable("cal_todos_v6", sqlTables["cal_todos"]);
  1053.                 this.mDB.createTable("cal_attendees_v6", sqlTables["cal_attendees"]);
  1054.                 this.mDB.createTable("cal_recurrence_v6", sqlTables["cal_recurrence"]);
  1055.                 this.mDB.createTable("cal_properties_v6", sqlTables["cal_properties"]);
  1056.  
  1057.  
  1058.                 // Copy in the data.
  1059.                 var cal_events_cols = ["cal_id", "id", "time_created",
  1060.                                        "last_modified", "title", "priority",
  1061.                                        "privacy", "ical_status",
  1062.                                        "recurrence_id", "recurrence_id_tz",
  1063.                                        "flags", "event_start",
  1064.                                        "event_start_tz", "event_end",
  1065.                                        "event_end_tz", "event_stamp",
  1066.                                        "alarm_time", "alarm_time_tz",
  1067.                                        "alarm_offset", "alarm_related",
  1068.                                        "alarm_last_ack"];
  1069.  
  1070.                 var cal_todos_cols = ["cal_id", "id", "time_created",
  1071.                                       "last_modified", "title", "priority",
  1072.                                       "privacy", "ical_status",
  1073.                                       "recurrence_id", "recurrence_id_tz",
  1074.                                       "flags", "todo_entry", "todo_entry_tz",
  1075.                                       "todo_due", "todo_due_tz",
  1076.                                       "todo_completed", "todo_completed_tz",
  1077.                                       "todo_complete", "alarm_time",
  1078.                                       "alarm_time_tz", "alarm_offset",
  1079.                                       "alarm_related", "alarm_last_ack"];
  1080.  
  1081.                 var cal_attendees_cols = ["item_id", "recurrence_id",
  1082.                                           "recurrence_id_tz", "attendee_id",
  1083.                                           "common_name", "rsvp", "role",
  1084.                                           "status", "type"];
  1085.  
  1086.                 var cal_recurrence_cols = ["item_id", "recur_index",
  1087.                                            "recur_type", "is_negative",
  1088.                                            "dates", "count", "end_date",
  1089.                                            "interval", "second", "minute",
  1090.                                            "hour", "day", "monthday",
  1091.                                            "yearday", "weekno", "month",
  1092.                                            "setpos"];
  1093.  
  1094.                 var cal_properties_cols = ["item_id", "recurrence_id",
  1095.                                            "recurrence_id_tz", "key",
  1096.                                            "value"];
  1097.  
  1098.                 var theDB = this.mDB;
  1099.                 function copyDataOver(aTableName, aColumnNames) {
  1100.                     theDB.executeSimpleSQL("INSERT INTO " + aTableName + "_v6(" + aColumnNames.join(",") + ") " + 
  1101.                                            "     SELECT " + aColumnNames.join(",") + 
  1102.                                            "       FROM " + aTableName + ";");
  1103.                 }
  1104.  
  1105.                 copyDataOver("cal_events", cal_events_cols);
  1106.                 copyDataOver("cal_todos", cal_todos_cols);
  1107.                 copyDataOver("cal_attendees", cal_attendees_cols);
  1108.                 copyDataOver("cal_recurrence", cal_recurrence_cols);
  1109.                 copyDataOver("cal_properties", cal_properties_cols);
  1110.  
  1111.  
  1112.                 // Delete each old table and rename the new ones to use the
  1113.                 // old tables' names.
  1114.                 for (var i in tableNames) {
  1115.                     this.mDB.executeSimpleSQL("DROP TABLE  " + tableNames[i] + ";" +
  1116.                                               "ALTER TABLE " + tableNames[i] + "_v6" + 
  1117.                                               "  RENAME TO " + tableNames[i] + ";");
  1118.                 }
  1119.  
  1120.  
  1121.                 // Update the version stamp, and commit.
  1122.                 this.mDB.executeSimpleSQL("UPDATE cal_calendar_schema_version SET version = 6;");
  1123.                 this.mDB.commitTransaction();
  1124.                 oldVersion = 6;
  1125.             } catch (e) {
  1126.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  1127.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  1128.                                              this.mDB.lastErrorString);
  1129.                 this.mDB.rollbackTransaction();
  1130.                 throw e;
  1131.             }
  1132.         }
  1133.  
  1134.         // run TZID updates both on db of version 6 and 7:
  1135.         if (oldVersion == 6 || oldVersion == 7) {
  1136.             dump ("**** Upgrading schema from 6/7 -> 8\n");
  1137.  
  1138.             var getTzIds;
  1139.             this.mDB.beginTransaction();
  1140.             try {
  1141.                 // Schema changes between v6 and v7:
  1142.                 //
  1143.                 // - Migrate all stored mozilla.org timezones from 20050126_1
  1144.                 //   to 20070129_1.  Note that there are some exceptions where
  1145.                 //   timezones were deleted and/or renamed.
  1146.  
  1147.                 // Schema changes between v7 and v8:
  1148.                 //
  1149.                 // - Migrate all stored mozilla.org timezones from 20070129_1
  1150.                 //   to 20071231_1.
  1151.  
  1152.                 // Get a list of the /mozilla.org/* timezones used in the db
  1153.                 var tzId;
  1154.                 getTzIds = createStatement(this.mDB,
  1155.                     "SELECT DISTINCT(zone) FROM ("+
  1156.                         "SELECT recurrence_id_tz AS zone FROM cal_attendees  WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1157.                         "SELECT recurrence_id_tz AS zone FROM cal_events     WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1158.                         "SELECT event_start_tz   AS zone FROM cal_events     WHERE event_start_tz   LIKE '/mozilla.org%' UNION " +
  1159.                         "SELECT event_end_tz     AS zone FROM cal_events     WHERE event_end_tz     LIKE '/mozilla.org%' UNION " +
  1160.                         "SELECT alarm_time_tz    AS zone FROM cal_events     WHERE alarm_time_tz    LIKE '/mozilla.org%' UNION " +
  1161.                         "SELECT recurrence_id_tz AS zone FROM cal_properties WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1162.                         "SELECT recurrence_id_tz AS zone FROM cal_todos      WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1163.                         "SELECT todo_entry_tz    AS zone FROM cal_todos      WHERE todo_entry_tz    LIKE '/mozilla.org%' UNION " +
  1164.                         "SELECT todo_due_tz      AS zone FROM cal_todos      WHERE todo_due_tz      LIKE '/mozilla.org%' UNION " +
  1165.                         "SELECT alarm_time_tz    AS zone FROM cal_todos      WHERE alarm_time_tz    LIKE '/mozilla.org%'" +
  1166.                     ");");
  1167.  
  1168.                 var tzIdsToUpdate = [];
  1169.                 var updateTzIds = false; // Perform the SQL UPDATE, or not.
  1170.                 while (getTzIds.step()) {
  1171.                     tzId = getTzIds.row.zone;
  1172.  
  1173.                     // Send the timezones off to the timezone service to attempt conversion.
  1174.                     var tz = getTimezoneService().getTimezone(tzId);
  1175.                     if (tz && (tzId != tz.tzid)) {
  1176.                         tzIdsToUpdate.push({oldTzId: tzId, newTzId: tz.tzid});
  1177.                         updateTzIds = true;
  1178.                     }
  1179.                 }
  1180.                 getTzIds.reset();
  1181.  
  1182.                 if (updateTzIds) {
  1183.                     // We've got stuff to update!
  1184.                     for each (var update in tzIdsToUpdate) {
  1185.                         this.mDB.executeSimpleSQL(
  1186.                             "UPDATE cal_attendees  SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1187.                             "UPDATE cal_events     SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1188.                             "UPDATE cal_events     SET event_start_tz   = '" + update.newTzId + "' WHERE event_start_tz   = '" + update.oldTzId + "'; " +
  1189.                             "UPDATE cal_events     SET event_end_tz     = '" + update.newTzId + "' WHERE event_end_tz     = '" + update.oldTzId + "'; " +
  1190.                             "UPDATE cal_events     SET alarm_time_tz    = '" + update.newTzId + "' WHERE alarm_time_tz    = '" + update.oldTzId + "'; " +
  1191.                             "UPDATE cal_properties SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1192.                             "UPDATE cal_todos      SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1193.                             "UPDATE cal_todos      SET todo_entry_tz    = '" + update.newTzId + "' WHERE todo_entry_tz    = '" + update.oldTzId + "'; " +
  1194.                             "UPDATE cal_todos      SET todo_due_tz      = '" + update.newTzId + "' WHERE todo_due_tz      = '" + update.oldTzId + "'; " +
  1195.                             "UPDATE cal_todos      SET alarm_time_tz    = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "';");
  1196.                     }
  1197.                 }
  1198.                 // Update the version stamp, and commit.
  1199.                 this.mDB.executeSimpleSQL("UPDATE cal_calendar_schema_version SET version = 8;");
  1200.                 this.mDB.commitTransaction();
  1201.                 oldVersion = 8;
  1202.             } catch (e) {
  1203.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  1204.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  1205.                                              this.mDB.lastErrorString);
  1206.                 this.mDB.rollbackTransaction();
  1207.                 throw e;
  1208.             }
  1209.         }
  1210.  
  1211.         if (oldVersion != this.DB_SCHEMA_VERSION) {
  1212.             dump ("#######!!!!! calStorageCalendar Schema Update failed -- db version: " + oldVersion + " this version: " + this.DB_SCHEMA_VERSION + "\n");
  1213.             throw Components.results.NS_ERROR_FAILURE;
  1214.         }
  1215.     },
  1216.  
  1217.     // database initialization
  1218.     // assumes mDB is valid
  1219.  
  1220.     initDB: function () {
  1221.         ASSERT(this.mDB, "Database has not been opened!", true);
  1222.         if (!this.mDB.tableExists("cal_calendar_schema_version")) {
  1223.             this.initDBSchema();
  1224.         } else {
  1225.             var version = this.getVersion();
  1226.             if (version != this.DB_SCHEMA_VERSION) {
  1227.                 this.upgradeDB(version);
  1228.             }
  1229.         }
  1230.         
  1231.         // (Conditionally) add index
  1232.         this.mDB.executeSimpleSQL(
  1233.             "CREATE INDEX IF NOT EXISTS " + 
  1234.             "idx_cal_properies_item_id ON cal_properties(item_id);"
  1235.             );
  1236.  
  1237.         this.mSelectEvent = createStatement (
  1238.             this.mDB,
  1239.             "SELECT * FROM cal_events " +
  1240.             "WHERE id = :id AND recurrence_id IS NULL " +
  1241.             "LIMIT 1"
  1242.             );
  1243.  
  1244.         this.mSelectTodo = createStatement (
  1245.             this.mDB,
  1246.             "SELECT * FROM cal_todos " +
  1247.             "WHERE id = :id AND recurrence_id IS NULL " +
  1248.             "LIMIT 1"
  1249.             );
  1250.  
  1251.         // The more readable version of the next where-clause is:
  1252.         //   WHERE  ((event_end > :range_start OR
  1253.         //           (event_end = :range_start AND
  1254.         //           event_start = :range_start))
  1255.         //          AND event_start < :range_end)
  1256.         //         
  1257.         // but that doesn't work with floating start or end times. The logic
  1258.         // is the same though.
  1259.         // For readability, a few helpers:
  1260.         var floatingEventStart = "event_start_tz = 'floating' AND event_start"
  1261.         var nonFloatingEventStart = "event_start_tz != 'floating' AND event_start"
  1262.         var floatingEventEnd = "event_end_tz = 'floating' AND event_end"
  1263.         var nonFloatingEventEnd = "event_end_tz != 'floating' AND event_end"
  1264.         // The query needs to take both floating and non floating into account
  1265.         this.mSelectNonRecurringEventsByRange = createStatement(
  1266.             this.mDB,
  1267.             "SELECT * FROM cal_events " +
  1268.             "WHERE " +
  1269.             " (("+floatingEventEnd+" > :range_start + :start_offset) OR " +
  1270.             "  ("+nonFloatingEventEnd+" > :range_start) OR " +
  1271.             "  ((("+floatingEventEnd+" = :range_start + :start_offset) OR " +
  1272.             "    ("+nonFloatingEventEnd+" = :range_start)) AND " +
  1273.             "   (("+floatingEventStart+" = :range_start + :start_offset) OR " +
  1274.             "    ("+nonFloatingEventStart+" = :range_start)))) " +
  1275.             " AND " +
  1276.             "  (("+floatingEventStart+" < :range_end + :end_offset) OR " +
  1277.             "   ("+nonFloatingEventStart+" < :range_end)) " +
  1278.             " AND cal_id = :cal_id AND flags & 16 == 0 AND recurrence_id IS NULL"
  1279.             );
  1280.        /**
  1281.         * WHERE (due > rangeStart AND start < rangeEnd) OR
  1282.         *       (due = rangeStart AND start = rangeStart) OR
  1283.         *       (due IS NULL AND ((start >= rangeStart AND start < rangeEnd) OR
  1284.         *                         (start IS NULL AND 
  1285.         *                          (completed > rangeStart OR completed IS NULL))) OR
  1286.         *       (start IS NULL AND due >= rangeStart AND due < rangeEnd)
  1287.         */
  1288.  
  1289.         var floatingTodoEntry = "todo_entry_tz = 'floating' AND todo_entry";
  1290.         var nonFloatingTodoEntry = "todo_entry_tz != 'floating' AND todo_entry";
  1291.         var floatingTodoDue = "todo_due_tz = 'floating' AND todo_due";
  1292.         var nonFloatingTodoDue = "todo_due_tz != 'floating' AND todo_due";
  1293.         var floatingCompleted = "todo_completed_tz = 'floating' AND todo_completed";
  1294.         var nonFloatingCompleted = "todo_completed_tz != 'floating' AND todo_completed";
  1295.  
  1296.         this.mSelectNonRecurringTodosByRange = createStatement(
  1297.             this.mDB,
  1298.             "SELECT * FROM cal_todos " +
  1299.             "WHERE " +
  1300.             "(((("+floatingTodoDue+" > :range_start + :start_offset) OR " +
  1301.             "   ("+nonFloatingTodoDue+" > :range_start)) AND " +
  1302.             "  (("+floatingTodoEntry+" < :range_end + :end_offset) OR " +
  1303.             "   ("+nonFloatingTodoEntry+" < :range_end))) OR " +
  1304.             " ((("+floatingTodoDue+" = :range_start + :start_offset) OR " +
  1305.             "   ("+nonFloatingTodoDue+" = :range_start)) AND " +
  1306.             "  (("+floatingTodoEntry+" = :range_start + :start_offset) OR " +
  1307.             "   ("+nonFloatingTodoEntry+" = :range_start))) OR " +
  1308.             " ((todo_due IS NULL) AND " +
  1309.             "  (((("+floatingTodoEntry+" >= :range_start + :start_offset) OR " +
  1310.             "    ("+nonFloatingTodoEntry+" >= :range_start)) AND " +
  1311.             "    (("+floatingTodoEntry+" < :range_end + :end_offset) OR " +
  1312.             "     ("+nonFloatingTodoEntry+" < :range_end))) OR " +
  1313.             "   ((todo_entry IS NULL) AND " +
  1314.             "    ((("+floatingCompleted+" > :range_start + :start_offset) OR " +
  1315.             "      ("+nonFloatingCompleted+" > :range_start)) OR " +
  1316.             "     (todo_completed IS NULL))))) OR " +
  1317.             " ((todo_entry IS NULL) AND " +
  1318.             "  (("+floatingTodoDue+" >= :range_start + :start_offset) OR " +
  1319.             "   ("+nonFloatingTodoDue+" >= :range_start)) AND " +
  1320.             "  (("+floatingTodoDue+" < :range_end + :end_offset) OR " +
  1321.             "   ("+nonFloatingTodoDue+" < :range_end)))) " +
  1322.             " AND cal_id = :cal_id AND flags & 16 == 0 AND recurrence_id IS NULL"
  1323.             );
  1324.  
  1325.         this.mSelectEventsWithRecurrence = createStatement(
  1326.             this.mDB,
  1327.             "SELECT * FROM cal_events " +
  1328.             " WHERE flags & 16 == 16 " +
  1329.             "   AND cal_id = :cal_id AND recurrence_id is NULL"
  1330.             );
  1331.  
  1332.         this.mSelectTodosWithRecurrence = createStatement(
  1333.             this.mDB,
  1334.             "SELECT * FROM cal_todos " +
  1335.             " WHERE flags & 16 == 16 " +
  1336.             "   AND cal_id = :cal_id AND recurrence_id IS NULL"
  1337.             );
  1338.  
  1339.         this.mSelectEventExceptions = createStatement (
  1340.             this.mDB,
  1341.             "SELECT * FROM cal_events " +
  1342.             "WHERE id = :id AND recurrence_id IS NOT NULL"
  1343.             );
  1344.  
  1345.         this.mSelectTodoExceptions = createStatement (
  1346.             this.mDB,
  1347.             "SELECT * FROM cal_todos " +
  1348.             "WHERE id = :id AND recurrence_id IS NOT NULL"
  1349.             );
  1350.  
  1351.         // For the extra-item data, note that we use mDBTwo, so that
  1352.         // these can be executed while a selectItems is running!
  1353.         this.mSelectAttendeesForItem = createStatement(
  1354.             this.mDBTwo,
  1355.             "SELECT * FROM cal_attendees " +
  1356.             "WHERE item_id = :item_id AND recurrence_id IS NULL"
  1357.             );
  1358.  
  1359.         this.mSelectAttendeesForItemWithRecurrenceId = createStatement(
  1360.             this.mDBTwo,
  1361.             "SELECT * FROM cal_attendees " +
  1362.             "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
  1363.             );
  1364.  
  1365.         this.mSelectPropertiesForItem = createStatement(
  1366.             this.mDBTwo,
  1367.             "SELECT * FROM cal_properties " +
  1368.             "WHERE item_id = :item_id AND recurrence_id IS NULL"
  1369.             );
  1370.  
  1371.         this.mSelectPropertiesForItemWithRecurrenceId = createStatement(
  1372.             this.mDBTwo,
  1373.             "SELECT * FROM cal_properties " +
  1374.             "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
  1375.             );
  1376.  
  1377.         this.mSelectRecurrenceForItem = createStatement(
  1378.             this.mDBTwo,
  1379.             "SELECT * FROM cal_recurrence " +
  1380.             "WHERE item_id = :item_id " +
  1381.             "ORDER BY recur_index"
  1382.             );
  1383.  
  1384.         // insert statements
  1385.         this.mInsertEvent = createStatement (
  1386.             this.mDB,
  1387.             "INSERT INTO cal_events " +
  1388.             "  (cal_id, id, time_created, last_modified, " +
  1389.             "   title, priority, privacy, ical_status, flags, " +
  1390.             "   event_start, event_start_tz, event_end, event_end_tz, event_stamp, " +
  1391.             "   alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz, " +
  1392.             "   alarm_offset, alarm_related, alarm_last_ack) " +
  1393.             "VALUES (:cal_id, :id, :time_created, :last_modified, " +
  1394.             "        :title, :priority, :privacy, :ical_status, :flags, " +
  1395.             "        :event_start, :event_start_tz, :event_end, :event_end_tz, :event_stamp, " +
  1396.             "        :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz," + 
  1397.             "        :alarm_offset, :alarm_related, :alarm_last_ack)"
  1398.             );
  1399.  
  1400.         this.mInsertTodo = createStatement (
  1401.             this.mDB,
  1402.             "INSERT INTO cal_todos " +
  1403.             "  (cal_id, id, time_created, last_modified, " +
  1404.             "   title, priority, privacy, ical_status, flags, " +
  1405.             "   todo_entry, todo_entry_tz, todo_due, todo_due_tz, todo_completed, " +
  1406.             "   todo_completed_tz, todo_complete, " +
  1407.             "   alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz, " +
  1408.             "   alarm_offset, alarm_related, alarm_last_ack)" +
  1409.             "VALUES (:cal_id, :id, :time_created, :last_modified, " +
  1410.             "        :title, :priority, :privacy, :ical_status, :flags, " +
  1411.             "        :todo_entry, :todo_entry_tz, :todo_due, :todo_due_tz, " +
  1412.             "        :todo_completed, :todo_completed_tz, :todo_complete, " +
  1413.             "        :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz," + 
  1414.             "        :alarm_offset, :alarm_related, :alarm_last_ack)"
  1415.             );
  1416.         this.mInsertProperty = createStatement (
  1417.             this.mDB,
  1418.             "INSERT INTO cal_properties (item_id, recurrence_id, recurrence_id_tz, key, value) " +
  1419.             "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :key, :value)"
  1420.             );
  1421.         this.mInsertAttendee = createStatement (
  1422.             this.mDB,
  1423.             "INSERT INTO cal_attendees " +
  1424.             "  (item_id, recurrence_id, recurrence_id_tz, attendee_id, common_name, rsvp, role, status, type) " +
  1425.             "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :attendee_id, :common_name, :rsvp, :role, :status, :type)"
  1426.             );
  1427.         this.mInsertRecurrence = createStatement (
  1428.             this.mDB,
  1429.             "INSERT INTO cal_recurrence " +
  1430.             "  (item_id, recur_index, recur_type, is_negative, dates, count, end_date, interval, second, minute, hour, day, monthday, yearday, weekno, month, setpos) " +
  1431.             "VALUES (:item_id, :recur_index, :recur_type, :is_negative, :dates, :count, :end_date, :interval, :second, :minute, :hour, :day, :monthday, :yearday, :weekno, :month, :setpos)"
  1432.             );
  1433.  
  1434.         // delete statements
  1435.         this.mDeleteEvent = createStatement (
  1436.             this.mDB,
  1437.             "DELETE FROM cal_events WHERE id = :id"
  1438.             );
  1439.         this.mDeleteTodo = createStatement (
  1440.             this.mDB,
  1441.             "DELETE FROM cal_todos WHERE id = :id"
  1442.             );
  1443.         this.mDeleteAttendees = createStatement (
  1444.             this.mDB,
  1445.             "DELETE FROM cal_attendees WHERE item_id = :item_id"
  1446.             );
  1447.         this.mDeleteProperties = createStatement (
  1448.             this.mDB,
  1449.             "DELETE FROM cal_properties WHERE item_id = :item_id"
  1450.             );
  1451.         this.mDeleteRecurrence = createStatement (
  1452.             this.mDB,
  1453.             "DELETE FROM cal_recurrence WHERE item_id = :item_id"
  1454.             );
  1455.  
  1456.         // These are only used when deleting an entire calendar
  1457.         var extrasTables = [ "cal_attendees", "cal_properties", "cal_recurrence" ];
  1458.  
  1459.         this.mDeleteEventExtras = new Array();
  1460.         this.mDeleteTodoExtras = new Array();
  1461.  
  1462.         for (var table in extrasTables) {
  1463.             this.mDeleteEventExtras[table] = createStatement (
  1464.                 this.mDB,
  1465.                 "DELETE FROM " + extrasTables[table] + " WHERE item_id IN" +
  1466.                 "  (SELECT id FROM cal_events WHERE cal_id = :cal_id)"
  1467.                 );
  1468.             this.mDeleteTodoExtras[table] = createStatement (
  1469.                 this.mDB,
  1470.                 "DELETE FROM " + extrasTables[table] + " WHERE item_id IN" +
  1471.                 "  (SELECT id FROM cal_todos WHERE cal_id = :cal_id)"
  1472.                 );
  1473.         }
  1474.  
  1475.         // Note that you must delete the "extras" _first_ using the above two
  1476.         // statements, before you delete the events themselves.
  1477.         this.mDeleteAllEvents = createStatement (
  1478.             this.mDB,
  1479.             "DELETE from cal_events WHERE cal_id = :cal_id"
  1480.             );
  1481.         this.mDeleteAllTodos = createStatement (
  1482.             this.mDB,
  1483.             "DELETE from cal_todos WHERE cal_id = :cal_id"
  1484.             );
  1485.  
  1486.     },
  1487.  
  1488.  
  1489.     //
  1490.     // database reading functions
  1491.     //
  1492.  
  1493.     // read in the common ItemBase attributes from aDBRow, and stick
  1494.     // them on item
  1495.     getItemBaseFromRow: function (row, flags, item) {
  1496.         item.calendar = this.superCalendar;
  1497.         item.id = row.id;
  1498.         if (row.title)
  1499.             item.title = row.title;
  1500.         if (row.priority)
  1501.             item.priority = row.priority;
  1502.         if (row.privacy)
  1503.             item.privacy = row.privacy;
  1504.         if (row.ical_status)
  1505.             item.status = row.ical_status;
  1506.  
  1507.         if (row.alarm_time) {
  1508.             // Old (schema version 4) data, need to convert this nicely to the
  1509.             // new alarm interface.  Eventually, we're going to want to be able
  1510.             // to deal with both types of data in a calIAlarm interface, but
  1511.             // not yet.  Leaving this column around though may help ease that
  1512.             // transition in the future.
  1513.             var alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
  1514.             var time;
  1515.             var related = Components.interfaces.calIItemBase.ALARM_RELATED_START;
  1516.             if (item instanceof Components.interfaces.calIEvent) {
  1517.                 time = newDateTime(row.event_start, row.event_start_tz);
  1518.             } else { //tasks
  1519.                 if (row.todo_entry) {
  1520.                     time = newDateTime(row.todo_entry, row.todo_entry_tz);
  1521.                 } else if (row.todo_due) {
  1522.                     related = Components.interfaces.calIItemBase.ALARM_RELATED_END;
  1523.                     time = newDateTime(row.todo_due, row.todo_due_tz);
  1524.                 }
  1525.             }
  1526.             if (time) {
  1527.                 var duration = alarmTime.subtractDate(time);
  1528.                 item.alarmOffset = duration;
  1529.                 item.alarmRelated = related;
  1530.             } else {
  1531.                 Components.utils.reportError("WARNING! Couldn't do alarm conversion for item:"+
  1532.                                              item.title+','+item.id+"!\n");
  1533.             }
  1534.         }
  1535.  
  1536.         // Alarm offset could be 0, but this is ok, so compare with null
  1537.         if (row.alarm_offset != null) {
  1538.             var duration = Components.classes["@mozilla.org/calendar/duration;1"]
  1539.                                      .createInstance(Components.interfaces.calIDuration);
  1540.             duration.inSeconds = row.alarm_offset;
  1541.             duration.normalize();
  1542.  
  1543.             item.alarmOffset = duration;
  1544.             item.alarmRelated = row.alarm_related;
  1545.             if (row.alarm_last_ack) {
  1546.                 // alarm acks are always in utc
  1547.                 item.alarmLastAck = newDateTime(row.alarm_last_ack, "UTC");
  1548.             }
  1549.         }
  1550.  
  1551.         if (row.recurrence_id)
  1552.             item.recurrenceId = newDateTime(row.recurrence_id, row.recurrence_id_tz);
  1553.  
  1554.         if (flags)
  1555.             flags.value = row.flags;
  1556.  
  1557.         if (row.time_created) {
  1558.             item.setProperty("CREATED", newDateTime(row.time_created, "UTC"));
  1559.         }
  1560.  
  1561.         // This must be done last because the setting of any other property
  1562.         // after this would overwrite it again.
  1563.         if (row.last_modified) {
  1564.             item.setProperty("LAST-MODIFIED", newDateTime(row.last_modified, "UTC"));
  1565.         }
  1566.     },
  1567.  
  1568.     cacheItem: function stor_cacheItem(item) {
  1569.         this.mItemCache[item.id] = item;
  1570.         if (item.recurrenceInfo) {
  1571.             if (isEvent(item)) {
  1572.                 this.mRecEventCache[item.id] = item;
  1573.             } else {
  1574.                 this.mRecTodoCache[item.id] = item;
  1575.             }
  1576.         }
  1577.     },
  1578.  
  1579.     assureRecurringItemCaches: function stor_assureRecurringItemCaches() {
  1580.         if (this.mRecItemCacheInited) {
  1581.             return;
  1582.         }
  1583.         // build up recurring event and todo cache, because we need that on every query:
  1584.         // for recurring items, we need to query database-wide.. yuck
  1585.  
  1586.         sp = this.mSelectEventsWithRecurrence.params;
  1587.         sp.cal_id = this.mCalId;
  1588.         while (this.mSelectEventsWithRecurrence.step()) {
  1589.             var row = this.mSelectEventsWithRecurrence.row;
  1590.             var item = this.getEventFromRow(row, {});
  1591.             this.mRecEventCache[item.id] = item;
  1592.         }
  1593.         this.mSelectEventsWithRecurrence.reset();
  1594.  
  1595.         sp = this.mSelectTodosWithRecurrence.params;
  1596.         sp.cal_id = this.mCalId;
  1597.         while (this.mSelectTodosWithRecurrence.step()) {
  1598.             var row = this.mSelectTodosWithRecurrence.row;
  1599.             var item = this.getTodoFromRow(row, {});
  1600.             this.mRecTodoCache[item.id] = item;
  1601.         }
  1602.         this.mSelectTodosWithRecurrence.reset();
  1603.  
  1604.         this.mRecItemCacheInited = true;
  1605.     },
  1606.  
  1607.     // xxx todo: consider removing flags parameter
  1608.     getEventFromRow: function stor_getEventFromRow(row, flags, isException) {
  1609.         var item;
  1610.         if (!isException) { // only parent items are cached
  1611.             item = this.mItemCache[row.id];
  1612.             if (item) {
  1613.                 return item;
  1614.             }
  1615.         }
  1616.  
  1617.         item = createEvent();
  1618.  
  1619.         if (row.event_start)
  1620.             item.startDate = newDateTime(row.event_start, row.event_start_tz);
  1621.         if (row.event_end)
  1622.             item.endDate = newDateTime(row.event_end, row.event_end_tz);
  1623.         if (row.event_stamp)
  1624.             item.setProperty("DTSTAMP", newDateTime(row.event_stamp, "UTC"));
  1625.         if ((row.flags & CAL_ITEM_FLAG_EVENT_ALLDAY) != 0) {
  1626.             item.startDate.isDate = true;
  1627.             item.endDate.isDate = true;
  1628.         }
  1629.  
  1630.         // This must be done last to keep the modification time intact.
  1631.         this.getItemBaseFromRow (row, flags, item);
  1632.         this.getAdditionalDataForItem(item, flags.value);
  1633.  
  1634.         if (!isException) { // keep exceptions modifyable to set the parentItem
  1635.             item.makeImmutable();
  1636.             this.cacheItem(item);
  1637.         }
  1638.         return item;
  1639.     },
  1640.  
  1641.     getTodoFromRow: function stor_getTodoFromRow(row, flags, isException) {
  1642.         var item;
  1643.         if (!isException) { // only parent items are cached
  1644.             item = this.mItemCache[row.id];
  1645.             if (item) {
  1646.                 return item;
  1647.             }
  1648.         }
  1649.  
  1650.         item = createTodo();
  1651.  
  1652.         if (row.todo_entry)
  1653.             item.entryDate = newDateTime(row.todo_entry, row.todo_entry_tz);
  1654.         if (row.todo_due)
  1655.             item.dueDate = newDateTime(row.todo_due, row.todo_due_tz);
  1656.         if (row.todo_completed)
  1657.             item.completedDate = newDateTime(row.todo_completed, row.todo_completed_tz);
  1658.         if (row.todo_complete)
  1659.             item.percentComplete = row.todo_complete;
  1660.  
  1661.         // This must be done last to keep the modification time intact.
  1662.         this.getItemBaseFromRow (row, flags, item);
  1663.         this.getAdditionalDataForItem(item, flags.value);
  1664.  
  1665.         if (!isException) { // keep exceptions modifyable to set the parentItem
  1666.             item.makeImmutable();
  1667.             this.cacheItem(item);
  1668.         }
  1669.         return item;
  1670.     },
  1671.  
  1672.     // after we get the base item, we need to check if we need to pull in
  1673.     // any extra data from other tables.  We do that here.
  1674.  
  1675.     // note that we use mDBTwo for this, so this can be run while a
  1676.     // select is executing; don't use any statements that aren't
  1677.     // against mDBTwo in here!
  1678.     
  1679.     getAdditionalDataForItem: function (item, flags) {
  1680.         // This is needed to keep the modification time intact.
  1681.         var savedLastModifiedTime = item.lastModifiedTime;
  1682.  
  1683.         if (flags & CAL_ITEM_FLAG_HAS_ATTENDEES) {
  1684.             var selectItem = null;
  1685.             if (item.recurrenceId == null)
  1686.                 selectItem = this.mSelectAttendeesForItem;
  1687.             else {
  1688.                 selectItem = this.mSelectAttendeesForItemWithRecurrenceId;
  1689.                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
  1690.             }
  1691.  
  1692.             selectItem.params.item_id = item.id;
  1693.  
  1694.             while (selectItem.step()) {
  1695.                 var attendee = this.getAttendeeFromRow(selectItem.row);
  1696.                 item.addAttendee(attendee);
  1697.             }
  1698.             selectItem.reset();
  1699.         }
  1700.  
  1701.         var row;
  1702.         if (flags & CAL_ITEM_FLAG_HAS_PROPERTIES) {
  1703.             var selectItem = null;
  1704.             if (item.recurrenceId == null)
  1705.                 selectItem = this.mSelectPropertiesForItem;
  1706.             else {
  1707.                 selectItem = this.mSelectPropertiesForItemWithRecurrenceId;
  1708.                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
  1709.             }
  1710.                 
  1711.             selectItem.params.item_id = item.id;
  1712.             
  1713.             while (selectItem.step()) {
  1714.                 row = selectItem.row;
  1715.                 var name = row.key;
  1716.                 if (name != "DURATION") {
  1717.                     // for events DTEND/DUE is enforced by calEvent/calTodo, so suppress DURATION:
  1718.                     item.setProperty(name, row.value);
  1719.                 }
  1720.             }
  1721.             selectItem.reset();
  1722.         }
  1723.  
  1724.         var i;
  1725.         if (flags & CAL_ITEM_FLAG_HAS_RECURRENCE) {
  1726.             if (item.recurrenceId)
  1727.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1728.  
  1729.             var rec = null;
  1730.  
  1731.             this.mSelectRecurrenceForItem.params.item_id = item.id;
  1732.             while (this.mSelectRecurrenceForItem.step()) {
  1733.                 row = this.mSelectRecurrenceForItem.row;
  1734.  
  1735.                 var ritem = null;
  1736.  
  1737.                 if (row.recur_type == null ||
  1738.                     row.recur_type == "x-dateset")
  1739.                 {
  1740.                     ritem = new CalRecurrenceDateSet();
  1741.  
  1742.                     var dates = row.dates.split(",");
  1743.                     for (i = 0; i < dates.length; i++) {
  1744.                         var date = textToDate(dates[i]);
  1745.                         ritem.addDate(date);
  1746.                     }
  1747.                 } else if (row.recur_type == "x-date") {
  1748.                     ritem = new CalRecurrenceDate();
  1749.                     var d = row.dates;
  1750.                     ritem.date = textToDate(d);
  1751.                 } else {
  1752.                     ritem = new CalRecurrenceRule();
  1753.  
  1754.                     ritem.type = row.recur_type;
  1755.                     if (row.count) {
  1756.                         try {
  1757.                             ritem.count = row.count;
  1758.                         } catch(exc) {
  1759.                         }
  1760.                     } else {
  1761.                         if (row.end_date)
  1762.                             ritem.endDate = newDateTime(row.end_date);
  1763.                         else
  1764.                             ritem.endDate = null;
  1765.                     }
  1766.                     try {
  1767.                         ritem.interval = row.interval;
  1768.                     } catch(exc) {
  1769.                     }
  1770.  
  1771.                     var rtypes = ["second",
  1772.                                   "minute",
  1773.                                   "hour",
  1774.                                   "day",
  1775.                                   "monthday",
  1776.                                   "yearday",
  1777.                                   "weekno",
  1778.                                   "month",
  1779.                                   "setpos"];
  1780.  
  1781.                     for (i = 0; i < rtypes.length; i++) {
  1782.                         var comp = "BY" + rtypes[i].toUpperCase();
  1783.                         if (row[rtypes[i]]) {
  1784.                             var rstr = row[rtypes[i]].toString().split(",");
  1785.                             var rarray = [];
  1786.                             for (var j = 0; j < rstr.length; j++) {
  1787.                                 rarray[j] = parseInt(rstr[j]);
  1788.                             }
  1789.  
  1790.                             ritem.setComponent (comp, rarray.length, rarray);
  1791.                         }
  1792.                     }
  1793.                 }
  1794.  
  1795.                 if (row.is_negative)
  1796.                     ritem.isNegative = true;
  1797.                 if (rec == null) {
  1798.                     rec = new CalRecurrenceInfo();
  1799.                     rec.item = item;
  1800.                 }
  1801.                 rec.appendRecurrenceItem(ritem);
  1802.             }
  1803.  
  1804.             if (rec == null) {
  1805.                 dump ("XXXX Expected to find recurrence, but got no items!\n");
  1806.             }
  1807.             item.recurrenceInfo = rec;
  1808.  
  1809.             this.mSelectRecurrenceForItem.reset();
  1810.         }
  1811.  
  1812.         if (flags & CAL_ITEM_FLAG_HAS_EXCEPTIONS) {
  1813.             // it's safe that we don't run into this branch again for exceptions
  1814.             // (getAdditionalDataForItem->get[Event|Todo]FromRow->getAdditionalDataForItem):
  1815.             // every excepton has a recurrenceId and isn't flagged as CAL_ITEM_FLAG_HAS_EXCEPTIONS
  1816.             if (item.recurrenceId)
  1817.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1818.  
  1819.             var rec = item.recurrenceInfo;
  1820.  
  1821.             if (item instanceof Components.interfaces.calIEvent) {
  1822.                 this.mSelectEventExceptions.params.id = item.id;
  1823.                 while (this.mSelectEventExceptions.step()) {
  1824.                     var row = this.mSelectEventExceptions.row;
  1825.                     var exc = this.getEventFromRow(row, {}, true /*isException*/);
  1826.                     exc.parentItem = item;
  1827.                     rec.modifyException(exc);
  1828.                 }
  1829.                 this.mSelectEventExceptions.reset();
  1830.             } else if (item instanceof Components.interfaces.calITodo) {
  1831.                 this.mSelectTodoExceptions.params.id = item.id;
  1832.                 while (this.mSelectTodoExceptions.step()) {
  1833.                     var row = this.mSelectTodoExceptions.row;
  1834.                     var exc = this.getTodoFromRow(row, {}, true /*isException*/);
  1835.                     exc.parentItem = item;
  1836.                     rec.modifyException(exc);
  1837.                 }
  1838.                 this.mSelectTodoExceptions.reset();
  1839.             } else {
  1840.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1841.             }
  1842.         }
  1843.  
  1844.         // Restore the saved modification time
  1845.         item.setProperty("LAST-MODIFIED", savedLastModifiedTime);
  1846.     },
  1847.  
  1848.     getAttendeeFromRow: function (row) {
  1849.         var a = CalAttendee();
  1850.  
  1851.         a.id = row.attendee_id;
  1852.         a.commonName = row.common_name;
  1853.         a.rsvp = (row.rsvp != 0);
  1854.         a.role = row.role;
  1855.         a.participationStatus = row.status;
  1856.         a.userType = row.type;
  1857.  
  1858.         return a;
  1859.     },
  1860.  
  1861.     //
  1862.     // get item from db or from cache with given iid
  1863.     //
  1864.     getItemById: function (aID) {
  1865.         this.assureRecurringItemCaches();
  1866.  
  1867.         // cached?
  1868.         var item = this.mItemCache[aID];
  1869.         if (item) {
  1870.             return item;
  1871.         }
  1872.  
  1873.         // not cached; need to read from the db
  1874.         var flags = {};
  1875.  
  1876.         // try events first
  1877.         this.mSelectEvent.params.id = aID;
  1878.         if (this.mSelectEvent.step())
  1879.             item = this.getEventFromRow(this.mSelectEvent.row, flags);
  1880.         this.mSelectEvent.reset();
  1881.  
  1882.         // try todo if event fails
  1883.         if (!item) {
  1884.             this.mSelectTodo.params.id = aID;
  1885.             if (this.mSelectTodo.step())
  1886.                 item = this.getTodoFromRow(this.mSelectTodo.row, flags);
  1887.             this.mSelectTodo.reset();
  1888.         }
  1889.  
  1890.         return item;
  1891.     },
  1892.  
  1893.     //
  1894.     // database writing functions
  1895.     //
  1896.  
  1897.     setDateParamHelper: function (params, entryname, cdt) {
  1898.         if (cdt) {
  1899.             params[entryname] = cdt.nativeTime;
  1900.             var tz = cdt.timezone;
  1901.             if (compareObjects(tz.provider, getTimezoneService())) {
  1902.                 params[entryname + "_tz"] = tz.tzid;
  1903.             } else { // foreign one
  1904.                 params[entryname + "_tz"] = tz.component.serializeToICS();
  1905.             }
  1906.         } else {
  1907.             params[entryname] = null;
  1908.             params[entryname + "_tz"] = null;
  1909.         }
  1910.     },
  1911.  
  1912.     flushItem: function (item, olditem) {
  1913.         ASSERT(!item.recurrenceId, "no parent item passed!", true);
  1914.  
  1915.         this.mDB.beginTransaction();
  1916.         try {
  1917.             this.deleteItemById(olditem ? olditem.id : item.id, true /* hasGuardingTransaction */);
  1918.             this.writeItem(item, olditem);
  1919.             this.mDB.commitTransaction();
  1920.         } catch (e) {
  1921.             dump("flushItem DB error: " + this.mDB.lastErrorString + "\n");
  1922.             Components.utils.reportError("flushItem DB error: " +
  1923.                                          this.mDB.lastErrorString);
  1924.             this.mDB.rollbackTransaction();
  1925.             throw e;
  1926.         }
  1927.  
  1928.         this.cacheItem(item);
  1929.     },
  1930.  
  1931.     //
  1932.     // The write* functions execute the database bits
  1933.     // to write the given item type.  They're to return
  1934.     // any bits they want or'd into flags, which will be passed
  1935.     // to writeEvent/writeTodo to actually do the writing.
  1936.     //
  1937.  
  1938.     writeItem: function (item, olditem) {
  1939.         var flags = 0;
  1940.  
  1941.         flags |= this.writeAttendees(item, olditem);
  1942.         flags |= this.writeRecurrence(item, olditem);
  1943.         flags |= this.writeProperties(item, olditem);
  1944.         flags |= this.writeAttachments(item, olditem);
  1945.  
  1946.         if (item instanceof Components.interfaces.calIEvent)
  1947.             this.writeEvent(item, olditem, flags);
  1948.         else if (item instanceof Components.interfaces.calITodo)
  1949.             this.writeTodo(item, olditem, flags);
  1950.         else
  1951.             throw Components.results.NS_ERROR_UNEXPECTED;
  1952.     },
  1953.  
  1954.     writeEvent: function (item, olditem, flags) {
  1955.         var ip = this.mInsertEvent.params;
  1956.         this.setupItemBaseParams(item, olditem,ip);
  1957.  
  1958.         this.setDateParamHelper(ip, "event_start", item.startDate);
  1959.         this.setDateParamHelper(ip, "event_end", item.endDate);
  1960.  
  1961.         if (item.startDate.isDate)
  1962.             flags |= CAL_ITEM_FLAG_EVENT_ALLDAY;
  1963.  
  1964.         ip.flags = flags;
  1965.  
  1966.         this.mInsertEvent.execute();
  1967.     },
  1968.  
  1969.     writeTodo: function (item, olditem, flags) {
  1970.         var ip = this.mInsertTodo.params;
  1971.  
  1972.         this.setupItemBaseParams(item, olditem,ip);
  1973.  
  1974.         this.setDateParamHelper(ip, "todo_entry", item.entryDate);
  1975.         this.setDateParamHelper(ip, "todo_due", item.dueDate);
  1976.         this.setDateParamHelper(ip, "todo_completed", item.getUnproxiedProperty("COMPLETED"));
  1977.  
  1978.         ip.todo_complete = item.getUnproxiedProperty("PERCENT-COMPLETED");
  1979.  
  1980.         ip.flags = flags;
  1981.  
  1982.         this.mInsertTodo.execute();
  1983.     },
  1984.  
  1985.     setupItemBaseParams: function (item, olditem, ip) {
  1986.         ip.cal_id = this.mCalId;
  1987.         ip.id = item.id;
  1988.  
  1989.         if (item.recurrenceId)
  1990.             this.setDateParamHelper(ip, "recurrence_id", item.recurrenceId);
  1991.  
  1992.         var tmp;
  1993.  
  1994.         if ((tmp = item.getUnproxiedProperty("CREATED")))
  1995.             ip.time_created = tmp.nativeTime;
  1996.         if ((tmp = item.getUnproxiedProperty("LAST-MODIFIED")))
  1997.             ip.last_modified = tmp.nativeTime;
  1998.  
  1999.         ip.title = item.getUnproxiedProperty("SUMMARY");
  2000.         ip.priority = item.getUnproxiedProperty("PRIORITY");
  2001.         ip.privacy = item.getUnproxiedProperty("CLASS");
  2002.         ip.ical_status = item.getUnproxiedProperty("STATUS");
  2003.  
  2004.         if (!item.parentItem)
  2005.             ip.event_stamp = item.stampTime.nativeTime;
  2006.  
  2007.         if (item.alarmOffset) {
  2008.             ip.alarm_offset = item.alarmOffset.inSeconds;
  2009.             ip.alarm_related = item.alarmRelated;
  2010.             if (item.alarmLastAck) {
  2011.                 ip.alarm_last_ack = item.alarmLastAck.nativeTime;
  2012.             }
  2013.         }
  2014.     },
  2015.  
  2016.     writeAttendees: function (item, olditem) {
  2017.         // XXX how does this work for proxy stuffs?
  2018.         var attendees = item.getAttendees({});
  2019.         if (attendees && attendees.length > 0) {
  2020.             for each (var att in attendees) {
  2021.                 var ap = this.mInsertAttendee.params;
  2022.                 ap.item_id = item.id;
  2023.                 this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
  2024.                 ap.attendee_id = att.id;
  2025.                 ap.common_name = att.commonName;
  2026.                 ap.rsvp = att.rsvp;
  2027.                 ap.role = att.role;
  2028.                 ap.status = att.participationStatus;
  2029.                 ap.type = att.userType;
  2030.  
  2031.                 this.mInsertAttendee.execute();
  2032.             }
  2033.  
  2034.             return CAL_ITEM_FLAG_HAS_ATTENDEES;
  2035.         }
  2036.  
  2037.         return 0;
  2038.     },
  2039.  
  2040.     writeProperties: function (item, olditem) {
  2041.         var ret = 0;
  2042.         var propEnumerator = item.unproxiedPropertyEnumerator;
  2043.         while (propEnumerator.hasMoreElements()) {
  2044.             ret = CAL_ITEM_FLAG_HAS_PROPERTIES;
  2045.  
  2046.             var prop = propEnumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
  2047.  
  2048.             if (item.isPropertyPromoted(prop.name))
  2049.                 continue;
  2050.  
  2051.             var pp = this.mInsertProperty.params;
  2052.  
  2053.             pp.key = prop.name;
  2054.             var pval = prop.value;
  2055.             if (pval instanceof Components.interfaces.calIDateTime) {
  2056.                 pp.value = pval.nativeTime;
  2057.             } else {
  2058.                 try {
  2059.                     pp.value = pval;
  2060.                 } catch (e) {
  2061.                     // The storage service throws an NS_ERROR_ILLEGAL_VALUE in
  2062.                     // case pval is something complex (i.e not a string or
  2063.                     // number). Swallow this error, leaving the value empty.
  2064.                     if (e.result != Components.results.NS_ERROR_ILLEGAL_VALUE) {
  2065.                         throw e;
  2066.                     }
  2067.                 }
  2068.             }
  2069.             pp.item_id = item.id;
  2070.             this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
  2071.  
  2072.             this.mInsertProperty.execute();
  2073.         }
  2074.  
  2075.         return ret;
  2076.     },
  2077.  
  2078.     writeRecurrence: function (item, olditem) {
  2079.         var flags = 0;
  2080.  
  2081.         var rec = item.recurrenceInfo;
  2082.         if (rec) {
  2083.             flags = CAL_ITEM_FLAG_HAS_RECURRENCE;
  2084.             var ritems = rec.getRecurrenceItems ({});
  2085.             for (i in ritems) {
  2086.                 var ritem = ritems[i];
  2087.                 ap = this.mInsertRecurrence.params;
  2088.                 ap.item_id = item.id;
  2089.                 ap.recur_index = i;
  2090.                 ap.is_negative = ritem.isNegative;
  2091.                 if (ritem instanceof kCalIRecurrenceDate) {
  2092.                     ritem = ritem.QueryInterface(kCalIRecurrenceDate);
  2093.                     ap.recur_type = "x-date";
  2094.                     ap.dates = dateToText(getInUtcOrKeepFloating(ritem.date));
  2095.  
  2096.                 } else if (ritem instanceof kCalIRecurrenceDateSet) {
  2097.                     ritem = ritem.QueryInterface(kCalIRecurrenceDateSet);
  2098.                     ap.recur_type = "x-dateset";
  2099.  
  2100.                     var rdates = ritem.getDates({});
  2101.                     var datestr = "";
  2102.                     for (j in rdates) {
  2103.                         if (j != 0)
  2104.                             datestr += ",";
  2105.  
  2106.                         datestr += dateToText(getInUtcOrKeepFloating(rdates[j]));
  2107.                     }
  2108.  
  2109.                     ap.dates = datestr;
  2110.  
  2111.                 } else if (ritem instanceof kCalIRecurrenceRule) {
  2112.                     ritem = ritem.QueryInterface(kCalIRecurrenceRule);
  2113.                     ap.recur_type = ritem.type;
  2114.  
  2115.                     if (ritem.isByCount)
  2116.                         ap.count = ritem.count;
  2117.                     else
  2118.                         ap.end_date = ritem.endDate ? ritem.endDate.nativeTime : null;
  2119.  
  2120.                     ap.interval = ritem.interval;
  2121.  
  2122.                     var rtypes = ["second",
  2123.                                   "minute",
  2124.                                   "hour",
  2125.                                   "day",
  2126.                                   "monthday",
  2127.                                   "yearday",
  2128.                                   "weekno",
  2129.                                   "month",
  2130.                                   "setpos"];
  2131.                     for (j = 0; j < rtypes.length; j++) {
  2132.                         var comp = "BY" + rtypes[j].toUpperCase();
  2133.                         var comps = ritem.getComponent(comp, {});
  2134.                         if (comps && comps.length > 0) {
  2135.                             var compstr = comps.join(",");
  2136.                             ap[rtypes[j]] = compstr;
  2137.                         }
  2138.                     }
  2139.                 } else {
  2140.                     dump ("##### Don't know how to serialize recurrence item " + ritem + "!\n");
  2141.                 }
  2142.  
  2143.                 this.mInsertRecurrence.execute();
  2144.             }
  2145.  
  2146.             var exceptions = rec.getExceptionIds ({});
  2147.             if (exceptions.length > 0) {
  2148.                 flags |= CAL_ITEM_FLAG_HAS_EXCEPTIONS;
  2149.  
  2150.                 // we need to serialize each exid as a separate
  2151.                 // event/todo; setupItemBase will handle
  2152.                 // writing the recurrenceId for us
  2153.                 for each (exid in exceptions) {
  2154.                     var ex = rec.getExceptionFor(exid, false);
  2155.                     if (!ex)
  2156.                         throw Components.results.NS_ERROR_UNEXPECTED;
  2157.                     this.writeItem(ex, null);
  2158.                 }
  2159.             }
  2160.         }
  2161.  
  2162.         return flags;
  2163.     },
  2164.  
  2165.     writeAttachments: function (item, olditem) {
  2166.         // XXX write me?
  2167.         return 0;
  2168.     },
  2169.  
  2170.     //
  2171.     // delete the item with the given uid
  2172.     //
  2173.     deleteItemById: function stor_deleteItemById(aID, hasGuardingTransaction) {
  2174.         if (!hasGuardingTransaction) {
  2175.             this.mDB.beginTransaction();
  2176.         }
  2177.         try {
  2178.             this.mDeleteAttendees(aID);
  2179.             this.mDeleteProperties(aID);
  2180.             this.mDeleteRecurrence(aID);
  2181.             this.mDeleteEvent(aID);
  2182.             this.mDeleteTodo(aID);
  2183.             if (!hasGuardingTransaction) {
  2184.                 this.mDB.commitTransaction();
  2185.             }
  2186.         } catch (e) {
  2187.             Components.utils.reportError("deleteItemById DB error: " + this.mDB.lastErrorString);
  2188.             if (!hasGuardingTransaction) {
  2189.                 this.mDB.rollbackTransaction();
  2190.             }
  2191.             throw e;
  2192.         }
  2193.  
  2194.         delete this.mItemCache[aID];
  2195.         delete this.mRecEventCache[aID];
  2196.         delete this.mRecTodoCache[aID];
  2197.     }
  2198. }
  2199.  
  2200. //
  2201. // sqlTables generated from schema.sql via makejsschema.pl
  2202. //
  2203.  
  2204. var sqlTables = {
  2205.   cal_calendar_schema_version:
  2206.     "    version    INTEGER" +
  2207.     "",
  2208.  
  2209.   cal_events:
  2210.     /*     REFERENCES cal_calendars.id, */
  2211.     "    cal_id        INTEGER, " +
  2212.     /*  ItemBase bits */
  2213.     "    id        TEXT," +
  2214.     "    time_created    INTEGER," +
  2215.     "    last_modified    INTEGER," +
  2216.     "    title        TEXT," +
  2217.     "    priority    INTEGER," +
  2218.     "    privacy        TEXT," +
  2219.     "    ical_status    TEXT," +
  2220.     "    recurrence_id    INTEGER," +
  2221.     "    recurrence_id_tz    TEXT," +
  2222.     /*  CAL_ITEM_FLAG_PRIVATE = 1 */
  2223.     /*  CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
  2224.     /*  CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
  2225.     /*  CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
  2226.     /*  CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
  2227.     /*  CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
  2228.     "    flags        INTEGER," +
  2229.     /*  Event bits */
  2230.     "    event_start    INTEGER," +
  2231.     "    event_start_tz    TEXT," +
  2232.     "    event_end    INTEGER," +
  2233.     "    event_end_tz    TEXT," +
  2234.     "    event_stamp    INTEGER," +
  2235.     /*  alarm time */
  2236.     "    alarm_time    INTEGER," +
  2237.     "    alarm_time_tz    TEXT," +
  2238.     "    alarm_offset    INTEGER," +
  2239.     "    alarm_related    INTEGER," +
  2240.     "    alarm_last_ack    INTEGER" +
  2241.     "",
  2242.  
  2243.   cal_todos:
  2244.     /*     REFERENCES cal_calendars.id, */
  2245.     "    cal_id        INTEGER, " +
  2246.     /*  ItemBase bits */
  2247.     "    id        TEXT," +
  2248.     "    time_created    INTEGER," +
  2249.     "    last_modified    INTEGER," +
  2250.     "    title        TEXT," +
  2251.     "    priority    INTEGER," +
  2252.     "    privacy        TEXT," +
  2253.     "    ical_status    TEXT," +
  2254.     "    recurrence_id    INTEGER," +
  2255.     "    recurrence_id_tz    TEXT," +
  2256.     /*  CAL_ITEM_FLAG_PRIVATE = 1 */
  2257.     /*  CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
  2258.     /*  CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
  2259.     /*  CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
  2260.     /*  CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
  2261.     /*  CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
  2262.     "    flags        INTEGER," +
  2263.     /*  Todo bits */
  2264.     /*  date the todo is to be displayed */
  2265.     "    todo_entry    INTEGER," +
  2266.     "    todo_entry_tz    TEXT," +
  2267.     /*  date the todo is due */
  2268.     "    todo_due    INTEGER," +
  2269.     "    todo_due_tz    TEXT," +
  2270.     /*  date the todo is completed */
  2271.     "    todo_completed    INTEGER," +
  2272.     "    todo_completed_tz TEXT," +
  2273.     /*  percent the todo is complete (0-100) */
  2274.     "    todo_complete    INTEGER," +
  2275.     /*  alarm time */
  2276.     "    alarm_time    INTEGER," +
  2277.     "    alarm_time_tz    TEXT," +
  2278.     "    alarm_offset    INTEGER," +
  2279.     "    alarm_related    INTEGER," +
  2280.     "    alarm_last_ack    INTEGER" +
  2281.     "",
  2282.  
  2283.   cal_attendees:
  2284.     "    item_id         TEXT," +
  2285.     "    recurrence_id    INTEGER," +
  2286.     "    recurrence_id_tz    TEXT," +
  2287.     "    attendee_id    TEXT," +
  2288.     "    common_name    TEXT," +
  2289.     "    rsvp        INTEGER," +
  2290.     "    role        TEXT," +
  2291.     "    status        TEXT," +
  2292.     "    type        TEXT" +
  2293.     "",
  2294.  
  2295.   cal_recurrence:
  2296.     "    item_id        TEXT," +
  2297.     /*  the index in the recurrence array of this thing */
  2298.     "    recur_index    INTEGER, " +
  2299.     /*  values from calIRecurrenceInfo; if null, date-based. */
  2300.     "    recur_type    TEXT, " +
  2301.     "    is_negative    BOOLEAN," +
  2302.     /*  */
  2303.     /*  these are for date-based recurrence */
  2304.     /*  */
  2305.     /*  comma-separated list of dates */
  2306.     "    dates        TEXT," +
  2307.     /*  */
  2308.     /*  these are for rule-based recurrence */
  2309.     /*  */
  2310.     "    count        INTEGER," +
  2311.     "    end_date    INTEGER," +
  2312.     "    interval    INTEGER," +
  2313.     /*  components, comma-separated list or null */
  2314.     "    second        TEXT," +
  2315.     "    minute        TEXT," +
  2316.     "    hour        TEXT," +
  2317.     "    day        TEXT," +
  2318.     "    monthday    TEXT," +
  2319.     "    yearday        TEXT," +
  2320.     "    weekno        TEXT," +
  2321.     "    month        TEXT," +
  2322.     "    setpos        TEXT" +
  2323.     "",
  2324.  
  2325.   cal_properties:
  2326.     "    item_id        TEXT," +
  2327.     "    recurrence_id    INTEGER," +
  2328.     "    recurrence_id_tz    TEXT," +
  2329.     "    key        TEXT," +
  2330.     "    value        BLOB" +
  2331.     ""
  2332.  
  2333. };
  2334.